import numpy as np
import cv2
import glob
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
%matplotlib inline
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d points in real world space
imgpoints = [] # 2d points in image plane.
# Make a list of calibration images
images = glob.glob('camera_cal/calibration*.jpg')
count = 0;
# Step through the list and search for chessboard corners
for fname in images:
img = cv2.imread(fname)
# Grayscale the image
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# Find the chessboard corners
ret, corners = cv2.findChessboardCorners(gray, (9,6),None)
# If found, add object points, image points
if ret == True:
objpoints.append(objp)
imgpoints.append(corners)
# Draw and display the corners
img = cv2.drawChessboardCorners(img, (9,6), corners, ret)
img = img[...,::-1] # RGB -> BGR
plt.figure()
plt.imshow(img)
outfile = 'Stage1_checkerdraw_%d.jpg' % (count)
cv2.imwrite(outfile, img)
count = count+1
# Camera calibration, given object points, image points, and the shape of the grayscale image
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
def undistort(image):
# Undistorting a test image
dst = cv2.undistort(image, mtx, dist, None, mtx)
return dst
# Check for one of the calibration image
print('Undistorting a test image')
image_file = 'camera_cal/calibration1.jpg'
img = cv2.imread(image_file)
img = img[...,::-1] # RGB -> BGR
undist = undistort(img)
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,8))
f.tight_layout()
ax1.set_title("Original Image", fontsize=30)
ax1.imshow(img)
cv2.imwrite('Stage2_OriginalImage.jpg', img)
ax2.set_title("Undistorted Image", fontsize=30)
ax2.imshow(undist)
cv2.imwrite('Stage2_UndistortedImage.jpg', undist)
# Try undistorting all the test images
images = glob.glob('test_images/test*.jpg')
for image in images:
img = cv2.imread(image)
img = img[...,::-1] # RGB -> BGR
undist = undistort(img)
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,8))
f.tight_layout()
ax1.set_title("Original Image", fontsize=30)
ax1.imshow(img)
ax2.set_title("Undistorted Image", fontsize=30)
ax2.imshow(undist)
import matplotlib.image as mpimg
# Edit this function to create your own pipeline.
def convertToBinary(image, r_thresh=(210, 255), s_thresh=(120, 255), sx_thresh=(20, 100)):
image = np.copy(image)
R = image[:,:,0]
r_binary = np.zeros_like(R)
r_binary[(R > r_thresh[0]) & (R <= r_thresh[1])] = 1
# Convert to HSV color space and separate the V channel
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HLS).astype(np.float)
l_channel = hsv[:,:,1]
s_channel = hsv[:,:,2]
sobelx = cv2.Sobel(l_channel, cv2.CV_64F, 1, 0) # Take the derivative in x
abs_sobelx = np.absolute(sobelx) # Absolute x derivative to accentuate lines away from horizontal
scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))
sxbinary = np.zeros_like(scaled_sobel)
sxbinary[(scaled_sobel >= sx_thresh[0]) & (scaled_sobel <= sx_thresh[1])] = 1
s_binary = np.zeros_like(s_channel)
s_binary[(s_channel >= s_thresh[0]) & (s_channel <= s_thresh[1])] = 1
# Stack each channel
color_binary = np.dstack(( np.zeros_like(sxbinary), sxbinary, r_binary))
combined_binary = np.zeros_like(sxbinary)
combined_binary[(r_binary == 1) | (sxbinary == 1)] = 1
return combined_binary
binary = convertToBinary(undist)
# Plot the result
print('Thresholded binary image')
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,8))
f.tight_layout()
ax1.imshow(undist)
ax1.set_title('Undistorted Image', fontsize=30)
ax2.imshow(binary, cmap='gray')
cv2.imwrite('Stage3_BinaryImage.jpg', binary * 255)
ax2.set_title('Binary Image', fontsize=30)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
def warper(image):
# Grab the image shape
img_size = (image.shape[1], image.shape[0])
offset = 350 # offset for dst points
# Source points
src = np.float32([[[ 610, 450]],
[[ 680, 450]],
[[ img_size[0]-300, 680]],
[[ 380, 680]]])
# Result points
dst = np.float32([[offset, 0],
[img_size[0]-offset, 0],
[img_size[0]-offset, img_size[1]],
[offset, img_size[1]]])
# calculate the perspective transform matrix
M = cv2.getPerspectiveTransform(src, dst)
# Warp the image
warped = cv2.warpPerspective(image, M, img_size)
return warped, M
warped, perspective_M = warper(binary)
# Plot the result
print('Top down image')
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,8))
f.tight_layout()
ax1.set_title("Binary Image", fontsize=30)
ax1.imshow(binary, cmap='gray')
ax2.set_title("Top down Image", fontsize=30)
ax2.imshow(warped, cmap='gray')
cv2.imwrite('Stage4_Warped.jpg', warped*255)
# window settings
window_width = 50
window_height = 80 # Break image into 9 vertical layers since image height is 720
margin = 100
def window_mask(width, height, img_ref, center,level):
output = np.zeros_like(img_ref)
output[int(img_ref.shape[0]-(level+1)*height):int(img_ref.shape[0]-level*height),max(0,int(center-width/2)):min(int(center+width/2),img_ref.shape[1])] = 1
return output
def find_window_centroids(image, window_width, window_height, margin):
window_centroids = [] # Store the (left,right) window centroid positions per level
leftx = []
rightx = []
window = np.ones(window_width) # Create our window template that we will use for convolutions
# First find the two starting positions for the left and right lane by using np.sum to get the vertical image slice
# and then np.convolve the vertical image slice with the window template
l_sum = np.sum(image[int(3*image.shape[0]/4):,:int(image.shape[1]/2)], axis=0)
l_center = np.argmax(np.convolve(window,l_sum))-window_width/2
r_sum = np.sum(image[int(3*image.shape[0]/4):,int(image.shape[1]/2):], axis=0)
r_center = np.argmax(np.convolve(window,r_sum))-window_width/2+int(image.shape[1]/2)
window_centroids.append((l_center,r_center))
leftx.append(l_center)
rightx.append(r_center)
# Go through each layer looking for max pixel locations
for level in range(1,(int)(image.shape[0]/window_height)):
# convolve the window into the vertical slice of the image
image_layer = np.sum(image[int(image.shape[0]-(level+1)*window_height):int(image.shape[0]-level*window_height),:], axis=0)
conv_signal = np.convolve(window, image_layer)
offset = window_width/2
l_min_index = int(max(l_center+offset-margin,0))
l_max_index = int(min(l_center+offset+margin,image.shape[1]))
prev_l_center = l_center
l_center = np.argmax(conv_signal[l_min_index:l_max_index])+l_min_index-offset
r_min_index = int(max(r_center+offset-margin,0))
r_max_index = int(min(r_center+offset+margin,image.shape[1]))
prev_r_center = r_center
r_center = np.argmax(conv_signal[r_min_index:r_max_index])+r_min_index-offset
window_centroids.append((l_center,r_center))
leftx.append(l_center)
rightx.append(r_center)
return window_centroids, leftx, rightx
window_centroids, leftx, rightx = find_window_centroids(warped, window_width, window_height, margin)
# If window center is found
if len(window_centroids) > 0:
# Points used to draw all the left and right windows
l_points = np.zeros_like(warped)
r_points = np.zeros_like(warped)
for level in range(0,len(window_centroids)):
l_mask = window_mask(window_width,window_height,warped,window_centroids[level][0],level)
r_mask = window_mask(window_width,window_height,warped,window_centroids[level][1],level)
# Add graphic points from window mask here to total pixels found
l_points[(l_points == 255) | ((l_mask == 1) ) ] = 255
r_points[(r_points == 255) | ((r_mask == 1) ) ] = 255
template = np.array(r_points+l_points,np.uint8) # add both left and right window pixels together
zero_channel = np.zeros_like(template) # create a zero color channel
template = np.array(cv2.merge((zero_channel,template,zero_channel)),np.uint8) # make window pixels green
out_img = np.dstack((warped, warped, warped))*255
warpage = np.array(out_img,np.uint8) # making the original road pixels 3 color channels
output = cv2.addWeighted(warpage, 1, template, 0.5, 0.0) # overlay the orignal road image with window results
# If no window centers found, just display orginal road image
else:
output = np.array(cv2.merge((warped,warped,warped)),np.uint8)
# Display the final results
print('Left and right points')
print ('Left: ',leftx)
print ('Right: ',rightx)
print('')
print('Fitted image')
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,8))
f.tight_layout()
ax1.set_title("Warped Image", fontsize=30)
ax1.imshow(warped, cmap='gray')
ax2.set_title("Fitted image", fontsize=30)
ax2.imshow(output)
cv2.imwrite('Stage5_Fitted.jpg', output*255)
ploty = np.linspace(0, 719, num=9)
def curvature(leftx, rightx):
leftx = np.asarray(leftx[::-1])
rightx = np.asarray(rightx[::-1])
# Fit a second order polynomial to pixel positions in each fake lane line
left_fit = np.polyfit(ploty, leftx, 2)
left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
leftx_int = left_fit[0]*720**2 + left_fit[1]*720 + left_fit[2]
right_fit = np.polyfit(ploty, rightx, 2)
right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
rightx_int = right_fit[0]*720**2 + right_fit[1]*720 + right_fit[2]
position = ((rightx_int+leftx_int)/2)-50
center = abs(640 - ((rightx_int+leftx_int)/2))
y_eval = np.max(ploty)
# Define conversions in x and y from pixels space to meters
ym_per_pix = 30/720 # meters per pixel in y dimension
xm_per_pix = 3.7/700 # meters per pixel in x dimension
# Fit new polynomials to x,y in world space
left_fit_cr = np.polyfit(ploty*ym_per_pix, leftx*xm_per_pix, 2)
right_fit_cr = np.polyfit(ploty*ym_per_pix, rightx*xm_per_pix, 2)
# Calculate the new radii of curvature
left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])
right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])
return position, center, leftx, rightx, left_fitx, right_fitx, left_curverad, right_curverad
position, center, leftx, rightx, left_fitx, right_fitx, left_curverad, right_curverad = curvature(leftx, rightx)
# Plot up the data
mark_size = 3
plt.plot(leftx, ploty, 'o', color='red', markersize=mark_size)
plt.plot(rightx, ploty, 'o', color='blue', markersize=mark_size)
plt.xlim(0, 1280)
plt.ylim(0, 720)
plt.plot(left_fitx, ploty, color='blue', linewidth=3)
plt.plot(right_fitx, ploty, color='blue', linewidth=3)
plt.gca().invert_yaxis() # to visualize as we do the images
plt.savefig('Stage6_DetCurv.jpg')
# Now our radius of curvature is in meters
print('Left:',left_curverad, 'm - Right:', right_curverad, 'm')
# Example values: 632.1 m 626.2 m
def warpBack(image, warp, persp_M, le_fitx, ri_fitx):
Minv = np.linalg.inv(persp_M)
warp_zero = np.zeros_like(warp).astype(np.uint8)
color_warp = np.dstack((warp_zero, warp_zero, warp_zero))
pts_left = np.array([np.transpose(np.vstack([le_fitx, ploty]))])
pts_right = np.array([np.flipud(np.transpose(np.vstack([ri_fitx, ploty])))])
pts = np.hstack((pts_left, pts_right))
cv2.fillPoly(color_warp, np.int_([pts]), (0,255,255))
newwarp = cv2.warpPerspective(color_warp, Minv, (image.shape[1], image.shape[0]))
result = cv2.addWeighted(image, 1, newwarp, 0.3, 0)
return result
warpBk = warpBack(undist, warped, perspective_M, left_fitx, right_fitx)
plt.imshow(warpBk)
warpBk = warpBk[...,::-1] # BGR -> RGB
cv2.imwrite('Stage7_Warpback.jpg', warpBk)
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,8))
f.tight_layout()
ax1.set_title("Original Image", fontsize=30)
ax1.imshow(img)
warpBck = warpBack(undist, warped, perspective_M, left_fitx, right_fitx)
ax2.set_title("Curvature and vehicle position", fontsize=30)
if position == 640:
cv2.putText(warpBck, 'Vehicle is in the center', (50,100),
fontFace = 16, fontScale = 1, color=(255,255,255), thickness = 2)
elif position < 640:
cv2.putText(warpBck, 'Vehicle is {:.2f}m left of center'.format(center*3.7/700), (50,100),
fontFace = 16, fontScale = 1, color=(255,255,255), thickness = 2)
else:
cv2.putText(warpBck, 'Vehicle is {:.2f}m right of center'.format(center*3.7/700), (50,100),
fontFace = 16, fontScale = 1, color=(255,255,255), thickness = 2)
cv2.putText(warpBck, 'Radius of Curvature {}(m)'.format(int((left_curverad + right_curverad)/2)), (50,140),
fontFace = 16, fontScale = 1, color=(255,255,255), thickness = 2)
ax2.imshow(warpBck)
warpBck = warpBck[...,::-1] # BGR -> RGB
cv2.imwrite('Stage8_DispCurvAndVehPosition.jpg', warpBck)
class laneline():
def __init__(self):
# was the line detected in the last iteration?
self.detected = False
# x values of the last n fits of the line
self.recent_xfitted = []
#average x values of the fitted line over the last n iterations
self.bestx = None
#polynomial coefficients averaged over the last n iterations
self.best_fit = None
#polynomial coefficients for the most recent fit
self.current_fit = [np.array([False])]
#radius of curvature of the line in some units
self.radius_of_curvature = None
#distance in meters of vehicle center from the line
self.line_base_pos = None
#difference in fit coefficients between last and new fits
self.diffs = np.array([0,0,0], dtype='float')
#x values for detected line pixels
self.allx = None
#y values for detected line pixels
self.ally = None
def pipeline(im):
undistorded = undistort(im)
final_binary = convertToBinary(undistorded)
final_warped, final_perspective_M = warper(final_binary)
window_cent, lex, rix = find_window_centroids(final_warped, window_width, window_height, margin)
position, center, leftx, rightx, left_lane.recent_xfitted, right_lane.recent_xfitted, left_lane.radius_of_curvature, right_lane.radius_of_curvature = curvature(lex, rix)
alpha = 0.5
if left_lane.bestx is None:
left_lane.bestx = left_lane.recent_xfitted
else :
left_lane.recent_xfitted = left_lane.recent_xfitted * ( 1 - alpha) + alpha * left_lane.bestx
if right_lane.bestx is None:
right_lane.bestx = right_lane.recent_xfitted
else :
right_lane.recent_xfitted = right_lane.recent_xfitted * ( 1 - alpha) + alpha * right_lane.bestx
#Sanity check
left_lane.detected = False
right_lane.detected = False
max_distance = 0
min_distance = 10000
for i in range(len(left_lane.recent_xfitted)) :
point_distance = right_lane.recent_xfitted[i] - left_lane.recent_xfitted[i]
if point_distance > max_distance:
max_distance = point_distance
if point_distance < min_distance:
min_distance = point_distance
if (min_distance > 710) and (max_distance < 900):
left_lane.detected = True
right_lane.detected = True
if not left_lane.detected:
left_lane.recent_xfitted = left_lane.bestx
else:
left_lane.bestx = left_lane.recent_xfitted
if not right_lane.detected:
right_lane.recent_xfitted = right_lane.bestx
else:
right_lane.bestx = right_lane.recent_xfitted
result = warpBack(im, final_warped, final_perspective_M, left_lane.recent_xfitted, right_lane.recent_xfitted)
# Print distance from center on video
if (center*3.7/700) < 0.01:
cv2.putText(result, 'Vehicle is in the center', (50,80),
fontFace = 16, fontScale = 1, color=(255,255,255), thickness = 2)
elif center < 640:
cv2.putText(result, 'Vehicle is {:.2f}m left of center'.format(center*3.7/700), (50,80),
fontFace = 16, fontScale = 1, color=(255,255,255), thickness = 2)
else:
cv2.putText(result, 'Vehicle is {:.2f}m right of center'.format(center*3.7/700), (50,80),
fontFace = 16, fontScale = 1, color=(255,255,255), thickness = 2)
# Print radius of curvature on video
cv2.putText(result, 'Radius of Curvature {}(m)'.format(int((left_lane.radius_of_curvature + right_lane.radius_of_curvature)/2)), (50,140),
fontFace = 16, fontScale = 1, color=(255,255,255), thickness = 2)
return result
# Test Final Pipeline
print('Test Final Pipeline')
left_lane = laneline()
right_lane = laneline()
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,8))
f.tight_layout()
ax1.set_title("Original Image", fontsize=30)
ax1.imshow(img)
ax2.set_title("After Pipeline", fontsize=30)
pipeline_imgoutput = pipeline(img)
ax2.imshow(pipeline_imgoutput)
pipeline_imgoutput = pipeline_imgoutput[...,::-1] # BGR -> RGB
cv2.imwrite('Stage9_CompleteImagePipeline.jpg', pipeline_imgoutput)
# Import everything needed to edit/save/watch video clips
from moviepy.editor import VideoFileClip
from IPython.display import HTML
left_lane = laneline()
right_lane = laneline()
white_output = 'project_video_result.mp4'
clip1 = VideoFileClip("project_video.mp4")#.subclip(38,43)
white_clip = clip1.fl_image(pipeline)
%time white_clip.write_videofile(white_output, audio=False)
HTML("""
<video width="960" height="540" controls>
<source src="{0}">
</video>
""".format(white_output))